#define lib_box2d_c
#define LUA_LIB

extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#include "lj_def.h" // Included always for the typedefs
#include "lj_lib.h"
}

#undef cast
#define GVL_EXCLUDE_STDINT 1 // lj2 has type typedefs

#include "box2d/world.hpp"
#include <Box2D/Dynamics/b2Body.h>
#include <Box2D/Collision/Shapes/b2CircleShape.h>
#include <Box2D/Collision/Shapes/b2PolygonShape.h>

#include <gvl/math/ieee.hpp>
#include "util.hpp"

extern "C" {

LUA_CHECK_FUNC(LuaWorld, world, UDTYPE_BOX2D_WORLD)

#define LJLIB_MODULE_box2d

// TODO: metatable and __gc

// TODO: Real userdata object for bodies (and other things). It must be invalidated
// when the body or the world is destroyed. The complication is that to invalidate one,
// we must store a pointer to it in the world. That pointer needs to be
// removed when the userdata is collected as well.

// Further complication: functions of the world that need to return bodies would
// either need to return the id or make the mapping to userdatas itself. The former
// would be pretty much what we have now and the latter is even more annoying.

LJLIB_CF(box2d_create_world)
{
	void* mem = lua_create_object_no_mt(L, UDTYPE_BOX2D_WORLD, sizeof(LuaWorld));
	LuaWorld* self = new (mem) LuaWorld();

	return 1;
}

LJLIB_CF(box2d_clone_world)
{
	auto& world = check_world(L, 1);

	void* mem = lua_create_object_no_mt(L, UDTYPE_BOX2D_WORLD, sizeof(LuaWorld));
	LuaWorld* self = new (mem) LuaWorld(world);

	return 1;
}

LJLIB_CF(box2d_world_destroy)
{
	if(LuaWorld* world = static_cast<LuaWorld*>(try_world(L, 1)))
	{
		lua_clear_object_tag(L, 1);
		world->~LuaWorld();
	}

	return 0;
}

LJLIB_CF(box2d_create_body)
{
	auto& world = check_world(L, 1);

	float32 x = float32(luaL_checknumber(L, 2));
	float32 y = float32(luaL_checknumber(L, 3));
	int isStatic = lua_toboolean(L, 4);

	unsigned id = world.nextBodyId++;

	b2BodyDef def;
	def.userData = (void*)id;
	def.position = b2Vec2(x, y);
	def.linearDamping = 0.f;
	def.angularDamping = 0.f;
	def.type = isStatic ? b2_staticBody : b2_dynamicBody;
	
	b2Body* body = world.CreateBody(&def);

	world.aliveBodies.insert(NumberedIndex<void*>(id, body));

	lua_pushinteger(L, id);
	return 1;
}

LJLIB_CF(box2d_world_step)
{
	auto& world = check_world(L, 1);

	float32 timeStep = float32(luaL_checknumber(L, 2));

	world.newContacts.clear();

	world.Step(timeStep, 10, 10);
	world.ClearForces();
	return 0;
}



LJLIB_CF(box2d_world_enable_contact_listener)
{
	auto& world = check_world(L, 1);

	world.SetContactListener(&world);

	return 0;
}

LJLIB_CF(box2d_world_get_new_contacts)
{
	auto& world = check_world(L, 1);

	lua_createtable(L, world.newContacts.size(), 0);
	GCtab* ret = tabV(L->top - 1);

	/* NOBARRIER: table is white */

	// TODO

	for(int i = 1; i <= world.newContacts.size(); ++i)
	{
		//setnumV(lj_tab_setint(L, ret, i), world.newContacts[i]->
	}

	return 0;
}

// Body functions

LJLIB_CF(box2d_body_add_circle)
{
	auto& world = check_world(L, 1);
	b2Body* body = world.check_body(L, 2);

	float32 offsx = float32(lj_lib_checknum(L, 3));
	float32 offsy = float32(lj_lib_checknum(L, 4));
	float32 radius = float32(lj_lib_checknum(L, 5));

	b2CircleShape circle;

	circle.m_p = b2Vec2(offsx, offsy);
	circle.m_radius = radius;

	unsigned id = world.nextShapeId++;

	b2FixtureDef fixtureDef;

	fixtureDef.userData = (void*)id;
	fixtureDef.density = 1.0f;
	fixtureDef.restitution = 0.2f;
	fixtureDef.friction = 0.5f;
	fixtureDef.shape = &circle;
	if(body->GetType() == b2_staticBody)
		fixtureDef.density = 0.f;

	b2Fixture* fixture = body->CreateFixture(&fixtureDef);

	world.aliveShapes.insert(NumberedIndex<void*>(id, fixture));

	lua_pushinteger(L, id);
	return 1;
}

LJLIB_CF(box2d_body_add_box)
{
	auto& world = check_world(L, 1);
	b2Body* body = world.check_body(L, 2);

	float32 offsx = float32(lj_lib_checknum(L, 3));
	float32 offsy = float32(lj_lib_checknum(L, 4));
	float32 width = float32(lj_lib_checknum(L, 5));
	float32 height = float32(lj_lib_checknum(L, 6));

	b2PolygonShape box;

	box.SetAsBox(width / 2.f, height / 2.f, b2Vec2(offsx, offsy), 0.f);

	unsigned id = world.nextShapeId++;

	b2FixtureDef fixtureDef;

	fixtureDef.userData = (void*)id;
	fixtureDef.density = 1.0f;
	fixtureDef.restitution = 0.2f;
	fixtureDef.friction = 0.5f;
	fixtureDef.shape = &box;
	if(body->GetType() == b2_staticBody)
		fixtureDef.density = 0.f;

	b2Fixture* fixture = body->CreateFixture(&fixtureDef);

	world.aliveShapes.insert(NumberedIndex<void*>(id, fixture));

	lua_pushinteger(L, id);
	return 1;
}

LJ_FUNCA void* LJ_FASTCALL lj_box2d_get_body(void* world, int id)
{
	return static_cast<LuaWorld*>(world)->get_body(id);
}

LJ_FUNCA double LJ_FASTCALL lj_box2d_get_body_x(void* body)
{
	return to_float(static_cast<b2Body*>(body)->GetPosition().x);
}

LJ_FUNCA double LJ_FASTCALL lj_box2d_get_body_y(void* body)
{
	return to_float(static_cast<b2Body*>(body)->GetPosition().y);
}

LJ_FUNCA double LJ_FASTCALL lj_box2d_get_body_ang(void* body)
{
	return to_float(static_cast<b2Body*>(body)->GetAngle());
}

LJLIB_CF(box2d_body_pos)
{
	auto& world = check_world(L, 1);
	b2Body* body = world.check_body(L, 2);

	auto& pos = body->GetPosition();

	lua_pushnumber(L, to_float(pos.x));
	lua_pushnumber(L, to_float(pos.y));
	return 2;
}

LJLIB_CF(box2d_body_orientation)  LJLIB_REC(box2d_core_body_orientation)
{
	auto& world = check_world(L, 1);
	b2Body* body = world.check_body(L, 2);

	auto& pos = body->GetPosition();
	float32 angle = body->GetAngle();

	setnumV(L->top++, to_float(pos.x));
	setnumV(L->top++, to_float(pos.y));
	setnumV(L->top++, to_float(angle));
	return 3;
}

/* ------------------------------------------------------------------------ */

#include "../lj_libdef.h"
#include "../lj_lib.h"

LUALIB_API int luaopen_box2d(lua_State *L)
{
	LJ_LIB_REG(L, box2d);
	return 1;
}

} // extern "C"
